package com.bandi.tharshesnows.parser;

import java.net.URL;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.ContentNode;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;

import android.location.Geocoder;
import android.util.Log;
import android.webkit.GeolocationPermissions;

import com.bandi.tharshesnows.GetDataCallback;
import com.bandi.tharshesnows.geolocating.GeocoderService;
import com.bandi.tharshesnows.http.HttpRequester;
import com.bandi.tharshesnows.model.County;
import com.bandi.tharshesnows.model.Resort;
import com.bandi.tharshesnows.model.Picture;
import com.bandi.tharshesnows.model.SkiTransport;
import com.bandi.tharshesnows.model.Skirun;
import com.bandi.tharshesnows.model.SkirunDifficulty;
import com.bandi.tharshesnows.model.SkirunStatus;

@Deprecated
public class RomaniaTuristicaParser {

  private static final String VALUE_METEO = "meteo";

  private static final String VALUE_PANES = "panes";

  private static final String TAG = "SNOW";

  private static final String ELEMENT_TD = "td";
  private static final String ELEMENT_TH = "th";
  private static final String ATTRIBUTE_HREF = "href";
  private static final String ELEMENT_SPAN = "span";
  private static final String ELEMENT_A = "a";
  private static final String ELEMENT_UL = "ul";
  private static final String ELEMENT_TR = "tr";
  private static final String VALUE_LABEL = "label";
  private static final String UPDATE_DATE_FORMAT = "dd/MM/yyyy HH:mm:ss";
  private static final String ELEMENT_LI = "li";
  private static final String VALUE_INFO_SCHI_CONTAINER = "infoSchiContainer";
  private static final String ATTRIBUTE_CLASS = "class";
  private static final String ATTRIBUTE_ID = "id";
  private static final String VALUE_TABLE_STARE_PARTII = "table_stare_partii";
  private static final String VALUE_TABLE_PARTII_LOC = "table_partii_loc";
  private static final String VALUE_TABLE_PARTII_LOC2 = "table_partii_loc2";

  private static final String COUNTY_NAME_PATTERN = "Judetul: {0}";
  private static final String LOCALITY_NAME_PATTERN = "Localitatea: {0}(actualizat {1})";
  private static final String SNOW_HEIGHT_PATTERN = "{0} cm";
  private static final String INCLINATION_PATTERN = "{0}&#37;";
  private static final String WIDTH_PATTERN = "{0} m";

  private String url;
  private String rootUrl;// "http://www.romaniaturistica.ro"
  private ExecutorService executorService = Executors.newFixedThreadPool(5);
  private static RomaniaTuristicaParser INSTANCE;

  private RomaniaTuristicaParser(String url, String rootUrl) {
    this.url = url;
    this.rootUrl = rootUrl;
  }

  public static void init(String url, String rootUrl) {
    if (INSTANCE == null) {
      INSTANCE = new RomaniaTuristicaParser(url, rootUrl);
    }
  }

  public static RomaniaTuristicaParser getParser() {
    if (INSTANCE == null) {
      throw new IllegalStateException("RomaniaTuristicaParser has not been initialized.");
    }
    return INSTANCE;
  }

  public static void destroy() {
    if (INSTANCE != null) {
      INSTANCE.executorService.shutdownNow();
    }
  }

  public void updateSkirun(final Skirun skirun, final GetDataCallback<Skirun> callback) {
    Runnable task = new Runnable() {
      @Override
      public void run() {
        try {
          CleanerProperties props = new CleanerProperties();
          props.setRecognizeUnicodeChars(true);
          HtmlCleaner cleaner = new HtmlCleaner(props);
          TagNode document = cleaner.clean(new URL(skirun.getRemoteURL()), "UTF-8");
          // get schedule
          try {
            TagNode panesNode = document.findElementByAttValue(ATTRIBUTE_CLASS, VALUE_PANES, true, true);
            TagNode[] divs = panesNode.getElementsByName("div", true);
            skirun.setSchedule(divs[0].getText().toString());
          }
          catch (Exception e) {
            Log.w(TAG, "Failed to get schedule.", e);
          }

          // get stats
          TagNode starePartiiTableNode = document.findElementByAttValue(ATTRIBUTE_ID, VALUE_TABLE_STARE_PARTII, true,
              true);
          TagNode[] starePartiiTableRows = starePartiiTableNode.getElementsByName(ELEMENT_TR, true);
          for (TagNode row : starePartiiTableRows) {
            // check if it has a date
            TagNode header = row.findElementByName(ELEMENT_TH, false);
            String headerTxt = header.getText().toString();
            try {
              if (headerTxt.contains("Actualizat")) {
                // contains date
                String rowValue = row.findElementByName(ELEMENT_TD, false).getText().toString();
                SimpleDateFormat dateFormat = new SimpleDateFormat(UPDATE_DATE_FORMAT);
                skirun.setUpdated(dateFormat.parse(rowValue));
              }
              else if (headerTxt.contains("Strat de zapada")) {
                // contains snow height
                String rowValue = row.findElementByName(ELEMENT_TD, false).getText().toString().trim();
                MessageFormat format = new MessageFormat(SNOW_HEIGHT_PATTERN);
                skirun.setSnowHeight(Double.parseDouble((String) format.parse(rowValue)[0]));
              }
            }
            catch (Throwable e) {
              Log.w(TAG, "Failed to parse update date.", e);
            }
          }

          // get SkiTransports
          TagNode[] partiiLocTableNodes2 = document.getElementsByAttValue(ATTRIBUTE_ID, VALUE_TABLE_PARTII_LOC2, true,
              true);
          TagNode[] partiiLocTableRows2 = partiiLocTableNodes2[0].getElementsByName(ELEMENT_TR, true);
          TagNode statsRow2 = partiiLocTableRows2[2];// 3rd row
          TagNode[] statsRowColumns2 = statsRow2.getElementsByName(ELEMENT_TD, false);
          // skitransports is in 2nd column
          try {
            Map<SkiTransport, Integer> skitransports = new HashMap<SkiTransport, Integer>();
            for (Object child : statsRowColumns2[1].getChildren()) {
              if (child instanceof ContentNode) {
                ContentNode content = (ContentNode) child;
                String skiTransport = content.getContent().toString().trim();
                MessageFormat format = new MessageFormat("{0} x {1}");
                Object[] tokens = format.parse(skiTransport);
                skitransports.put(SkiTransport.valueOf((String) tokens[1]), Integer.parseInt((String) tokens[0]));
              }
            }
            skirun.setSkiTransports(skitransports);
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse skitransports", e);
          }
          // canons are in 7th column
          try {
            String canons = statsRowColumns2[6].getText().toString().trim();
            skirun.setCanons(canons.equals("Da"));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse canons", e);
          }
          // night lights are in 8th column
          try {
            String lights = statsRowColumns2[7].getText().toString().trim();
            skirun.setNightmode(lights.equals("Da"));
          }
          catch (Throwable e) {
            Log.e(TAG, "Failed to parse lights", e);
          }
          // get location
          if (skirun.getLocation() == null || skirun.getLocation().getStaticMapImagePath() == null) {
            skirun.setLocation(GeocoderService.getInstance().getLocation(skirun));
          }
          // get observations
          partiiLocTableRows2 = partiiLocTableNodes2[1].getElementsByName(ELEMENT_TR, true);
          // second row contains observations
          TagNode obsRow = partiiLocTableRows2[1];// 2nd row
          skirun.setObservations(obsRow.findElementByName(ELEMENT_TD, true).getText().toString());
          callback.onData(skirun);
        }
        catch (Throwable t) {
          callback.onError(t);
        }
      }
    };
    executorService.execute(task);
  }

  public void getNewSkirun(final Skirun skirun, final GetDataCallback<Skirun> callback) {
    Runnable task = new Runnable() {
      @Override
      public void run() {
        try {
          CleanerProperties props = new CleanerProperties();
          props.setRecognizeUnicodeChars(true);
          HtmlCleaner cleaner = new HtmlCleaner(props);
          TagNode document = cleaner.clean(new URL(skirun.getRemoteURL()), "UTF-8");
          // get schedule
          try {
            TagNode panesNode = document.findElementByAttValue(ATTRIBUTE_CLASS, VALUE_PANES, true, true);
            TagNode[] divs = panesNode.getElementsByName("div", true);
            skirun.setSchedule(divs[0].getText().toString());
          }
          catch (Exception e) {
            Log.w(TAG, "Failed to get schedule.", e);
          }
          // get meteo
          try {
            TagNode meteoNode = document.findElementByAttValue(ATTRIBUTE_CLASS, VALUE_METEO, true, true);
            skirun.setMeteoURL(meteoNode.findElementByName("script", true).getAttributeByName("src"));
          }
          catch (Exception e) {
            Log.w(TAG, "Failed to get meteo.", e);
          }
          // get pictures
          try {
            List<TagNode> picNodes = document.getElementListByAttValue("rel", "lightbox[poze]", true, true);
            List<Picture> pics = new ArrayList<Picture>();
            for (TagNode picNode : picNodes) {
              Picture pic = new Picture();
              pic.setImageURL(rootUrl + picNode.getAttributeByName(ATTRIBUTE_HREF));
              TagNode imgTag = picNode.findElementByName("img", true);
              pic.setThumbURL(rootUrl + imgTag.getAttributeByName("src"));
              pics.add(pic);
            }
            skirun.setPictures(pics);
          }
          catch (Exception e) {
            Log.w(TAG, "Failed to get pictures.", e);
          }
          // get stats
          TagNode starePartiiTableNode = document.findElementByAttValue(ATTRIBUTE_ID, VALUE_TABLE_STARE_PARTII, true,
              true);
          TagNode[] starePartiiTableRows = starePartiiTableNode.getElementsByName(ELEMENT_TR, true);
          for (TagNode row : starePartiiTableRows) {
            // check if it has a date
            TagNode header = row.findElementByName(ELEMENT_TH, false);
            String headerTxt = header.getText().toString();
            try {
              if (headerTxt.contains("Actualizat")) {
                // contains date
                String rowValue = row.findElementByName(ELEMENT_TD, false).getText().toString();
                SimpleDateFormat dateFormat = new SimpleDateFormat(UPDATE_DATE_FORMAT);
                skirun.setUpdated(dateFormat.parse(rowValue));
              }
              else if (headerTxt.contains("Strat de zapada")) {
                // contains snow height
                String rowValue = row.findElementByName(ELEMENT_TD, false).getText().toString().trim();
                MessageFormat format = new MessageFormat(SNOW_HEIGHT_PATTERN);
                skirun.setSnowHeight(Double.parseDouble((String) format.parse(rowValue)[0]));
              }
            }
            catch (Throwable e) {
              Log.w(TAG, "Failed to parse update date.", e);
            }
          }
          TagNode partiiLocTableNode = document.findElementByAttValue(ATTRIBUTE_ID, VALUE_TABLE_PARTII_LOC, true, true);
          TagNode[] partiiLocTableRows = partiiLocTableNode.getElementsByName(ELEMENT_TR, true);
          TagNode statsRow = partiiLocTableRows[2];// 3rd row
          TagNode[] statsRowColumns = statsRow.getElementsByName(ELEMENT_TD, false);
          // difficulty is in 3rd column
          try {
            String difficultyString = statsRowColumns[2].getText().toString().trim();
            skirun.setDifficulty(SkirunDifficulty.parse(difficultyString));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse difficulty", e);
          }
          // length is in 4t column
          try {
            String lengthString = statsRowColumns[3].getText().toString().trim();
            skirun.setLength(Double.parseDouble(lengthString));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse length", e);
          }
          // starting altitude is in 5th column
          try {
            String startAlt = statsRowColumns[4].getText().toString().trim();
            skirun.setStartingAltitude(Double.parseDouble(startAlt));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse starting altitude", e);
          }
          // dest altitude is in 6th column
          try {
            String destAltitude = statsRowColumns[5].getText().toString().trim();
            skirun.setDestinationAltitude(Double.parseDouble(destAltitude));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse dest altitude", e);
          }
          // inclination is 7th column
          try {
            String incl = statsRowColumns[6].getText().toString().trim();
            MessageFormat format = new MessageFormat(INCLINATION_PATTERN);
            skirun.setInclination(Double.parseDouble((String) format.parse(incl)[0]));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse inclination", e);
          }
          // width is in 9th column
          try {
            String width = statsRowColumns[8].getText().toString().trim();
            MessageFormat format = new MessageFormat(WIDTH_PATTERN);
            skirun.setWidth(Double.parseDouble((String) format.parse(width)[0]));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse width", e);
          }
          // get SkiTransports
          TagNode[] partiiLocTableNodes2 = document.getElementsByAttValue(ATTRIBUTE_ID, VALUE_TABLE_PARTII_LOC2, true,
              true);
          TagNode[] partiiLocTableRows2 = partiiLocTableNodes2[0].getElementsByName(ELEMENT_TR, true);
          TagNode statsRow2 = partiiLocTableRows2[2];// 3rd row
          TagNode[] statsRowColumns2 = statsRow2.getElementsByName(ELEMENT_TD, false);
          // skitransports is in 2nd column
          try {
            Map<SkiTransport, Integer> skitransports = new HashMap<SkiTransport, Integer>();
            for (Object child : statsRowColumns2[1].getChildren()) {
              if (child instanceof ContentNode) {
                ContentNode content = (ContentNode) child;
                String skiTransport = content.getContent().toString().trim();
                MessageFormat format = new MessageFormat("{0} x {1}");
                Object[] tokens = format.parse(skiTransport);
                skitransports.put(SkiTransport.valueOf((String) tokens[1]), Integer.parseInt((String) tokens[0]));
              }
            }
            skirun.setSkiTransports(skitransports);
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse skitransports", e);
          }
          // canons are in 7th column
          try {
            String canons = statsRowColumns2[6].getText().toString().trim();
            skirun.setCanons(canons.equals("Da"));
          }
          catch (Throwable e) {
            Log.w(TAG, "Failed to parse canons", e);
          }
          // night lights are in 8th column
          try {
            String lights = statsRowColumns2[7].getText().toString().trim();
            skirun.setNightmode(lights.equals("Da"));
          }
          catch (Throwable e) {
            Log.e(TAG, "Failed to parse lights", e);
          }
          // get location
          skirun.setLocation(GeocoderService.getInstance().getLocation(skirun));
          // get observations
          partiiLocTableRows2 = partiiLocTableNodes2[1].getElementsByName(ELEMENT_TR, true);
          // second row contains observations
          TagNode obsRow = partiiLocTableRows2[1];// 2nd row
          skirun.setObservations(obsRow.findElementByName(ELEMENT_TD, true).getText().toString());
          callback.onData(skirun);
        }
        catch (Throwable t) {
          callback.onError(t);
        }
      }
    };
    executorService.execute(task);
  }

  public void parseAllLocations(final GetDataCallback<List<County>> callback) {
    Runnable task = new Runnable() {
      @Override
      public void run() {
        try {
          HtmlCleaner cleaner = new HtmlCleaner();
          TagNode document = cleaner.clean(new URL(url));
          List<TagNode> countyNodes = getCountyNodes(document);
          List<County> counties = new ArrayList<County>();
          for (TagNode countyNode : countyNodes) {
            // getcounty name;
            County county = new County();
            county.setName(getCountyNameFromNode(countyNode));
            counties.add(county);
            // get location
            if (county.getLocation() == null || county.getLocation().getStaticMapImagePath() == null) {
              county.setLocation(GeocoderService.getInstance().getLocation(county));
            }

            // get locations - they are located in the second html element (ul)
            List<Resort> localities = new ArrayList<Resort>();
            // get li elements
            List<TagNode> localityNodes = getLocalityNodesForCounty(countyNode);
            for (TagNode localityNode : localityNodes) {
              // span with class = label contains location name.
              Resort locality = new Resort(county);
              try {// try to parse a locality
                TagNode localityNameSpan = localityNode.findElementByAttValue(ATTRIBUTE_CLASS, VALUE_LABEL, false, true);
                MessageFormat localityParser = new MessageFormat(LOCALITY_NAME_PATTERN);
                Object[] tokens = localityParser.parse(localityNameSpan.getText().toString());
                locality.setName((String) tokens[0]);
                // parse date
                SimpleDateFormat dateFormat = new SimpleDateFormat(UPDATE_DATE_FORMAT);
                locality.setUpdated(dateFormat.parse((String) tokens[1]));
                localities.add(locality);

                // get skiruns
                List<TagNode> skiRunNodes = getSkirunNodesForLocality(localityNode);
                List<Skirun> skiruns = new ArrayList<Skirun>();
                for (TagNode skirunNode : skiRunNodes) {
                  Skirun skirun = new Skirun(locality);
                  try {// try to parse skirun
                    TagNode linkNode = skirunNode.findElementByName(ELEMENT_A, false);
                    String openClosed = linkNode.findElementByName(ELEMENT_SPAN, false).findElementByName(ELEMENT_SPAN,
                        false).getText().toString();
                    String linkNodeText = linkNode.getText().toString();
                    String linkNodeHref = linkNode.getAttributeByName(ATTRIBUTE_HREF);
                    skirun.setRemoteURL(rootUrl + linkNodeHref);
                    skirun.setName(linkNodeText.replace(openClosed, ""));
                    skirun.setSkirunStatus(SkirunStatus.parse(openClosed));
                    skiruns.add(skirun);
                  }
                  catch (Throwable e) {
                    // failed to load a skirun
                  }
                }
                locality.setSkiruns(skiruns);
              }
              catch (Throwable t) {
                // failed to parse locality
              }
            }
            county.setResorts(localities);
          }

          callback.onData(counties);
        }
        catch (Throwable e) {
          callback.onError(e);
        }
      }
    };
    executorService.execute(task);
  }

  // County methods

  private List<TagNode> getCountyNodes(TagNode root) {
    return root.getElementListByAttValue(ATTRIBUTE_CLASS, VALUE_INFO_SCHI_CONTAINER, true, true);
  }

  private String getCountyNameFromNode(TagNode countyNode) throws ParseException {
    TagNode countyName = countyNode.getAllElements(false)[0];
    MessageFormat format = new MessageFormat(COUNTY_NAME_PATTERN);
    return (String) format.parse(countyName.getText().toString())[0];
  }

  // locations

  private List<TagNode> getLocalityNodesForCounty(TagNode countyNode) {
    return countyNode.getAllElements(false)[1].getElementListByName(ELEMENT_LI, false);
  }

  private List<TagNode> getSkirunNodesForLocality(TagNode localityNode) {
    TagNode list = localityNode.findElementByName(ELEMENT_UL, false);
    List<TagNode> listItems = list.getElementListByName(ELEMENT_LI, false);
    return listItems;
  }

}
